#Tutorial by Ian Mallett

#Controls:
#ESC           - Return to menu
#LCLICK + DRAG - Rotate camera
#SROLL WHEEL   - Zoom

#Theory:
#Lighting makes objects look 3D.  glLibLight(...) provides
#high level access to spotlighting, attenuation, and other
#light functions.  Using these features with a shader is
#shown.

import os
import sys

sys.path.insert(0, os.path.expanduser('~') + '/lib/python')

from mylib.glLib.glLibShader import *
from mylib.glLib.glLibWindow import *
from mylib.glLib.glLibView import *
from mylib.glLib.glLibLight import *
from mylib.glLib.glLibLocals import *
from mylib.glLib.glLibObjects import *


def init(Screen):
    global View3D, Plane, CameraRotation, CameraRadius, LightRadius, LightUpDown, LightMulti, \
        Light1, Light2, Light3, Light4, Shader, Normalmap, Object
    global dl
    View3D = glLibView3D((0,0,Screen[0],Screen[1]),45,0.1,1000)

    # A plane of size 5, with a normal facing straight up
    # i.e., the XZ plane
    Plane = glLibPlane(100,(0,1,0))
    try:
        # A object, loaded as before
        Object = glLibObject("data/objects/trisphere.obj")
        Object.build_list()
    except:
        print("OBJ load fail~")

    # Add variables for the camera's rotation and radius
    CameraRotation = [90, 23]

    CameraRadius = 8.0
    LightRadius = CameraRadius * 0.95
    LightUpDown = -0.5
    LightMulti = 1.0

    # Enable lighting.  This is required for fixed function
    # lighting, but as the lighting equation is specified
    # explicitly in the shader, it is not required.  Generally,
    # however, one leaves it on.
    glEnable(GL_LIGHTING)

    # Make a light object
    Light1 = glLibLight(1)
    # Set the light's position
    Light1.set_pos([0, 1.8, 0])
    # Make it a point light (as opposed to directional)
    Light1.set_type(GLLIB_POINT_LIGHT)
    # Set the light's attenuation by changing the three
    # constants in the denominator of the attenuation function.
    # Here, the function is:
    # 1.0 / (1.0 + 0.0*dist + 1.0*dist*dist)
    Light1.set_atten(1.0, 0.0, 1.0)
    # Enable the light.  Again, not strictly necessary for
    # the programmable pipeline.  Required for fixed function.
    Light1.enable()

    Light2 = glLibLight(2)
    Light2.set_pos([2, 0.1, 0])
    Light2.set_type(GLLIB_POINT_LIGHT)
    # The default diffuse and specular for the first light
    # are both [1,1,1].  However, they are both [0,0,0] for
    # subsequent lights, so we must set them here, while we
    # didn't for Light1.  See the OpenGL documentation.  Let's
    # make them red while we're at it.
    Light2.set_diffuse([0, 0, 1])
    Light2.set_specular([0, 0, 1])
    Light2.set_atten(1.0, 0.0, 1.0)
    Light2.enable()

    Light3 = glLibLight(3)
    Light3.set_pos([0.0, 1.0, 0.0])
    Light3.set_type(GLLIB_POINT_LIGHT)
    Light3.set_diffuse([0, 1, 0])
    Light3.set_specular([0, 1, 0])
    Light3.set_atten(1.0, 0.2, 0.0)
    # Make a spotlight.  Note the light_spot(...) requirement
    # in the shader below.  First, set the direction as a
    # normalized vector.  Then, make the spotlight have an
    # angle of 10 degrees (default 180; not a spotlight).
    # Then make the exponent of the spotlight.  The result is
    # a green light that projects a cone that, because it is
    # tilted, intersects the plane in an ellipse with softened
    # edges.
    # Light3.set_spot_dir(normalize([0, -1.0, 0]))
    Light3.set_spot_dir(normalize([0, 0, 0]))
    Light3.set_spot_angle(45.0)
    Light3.set_spot_ex(0.25)
    Light3.enable()

    Light4 = glLibLight(4)
    Light4.set_pos([-0.5, 1.0, -2.5])
    Light4.set_type(GLLIB_POINT_LIGHT)
    Light4.set_diffuse([1, 0, 0])
    Light4.set_specular([1, 0, 0])
    Light4.set_atten(1.0, 0.0, 1.0)
    Light4.set_spot_dir(normalize([-0.4, -1.0, 1.0]))
    Light4.set_spot_angle(45.0)
    Light4.set_spot_ex(0.1)
    Light4.enable()

    pygame.mouse.get_rel()

    dl = glGenLists(1)
    glNewList(dl, GL_COMPILE)
    glMaterialfv(GL_FRONT_AND_BACK, GL_DIFFUSE, [1, 1, 0.5, 1])
    glBegin(GL_QUADS)
    glNormal3f(0, 1, 0)
    glVertex3f(1, 0, 0)
    glVertex3f(2, 0, 0)
    glVertex3f(2, 0, 1)
    glVertex3f(1, 0, 1)
    glEnd()
    glEndList()

    # Normal OpenGL lighting is per-vertex, meaning that the
    # lighting is calculated at every vertex only and interpolated
    # across each polygon.  Because that looks weird sometimes,
    # this shader implement per-pixel lighting, which evaluates
    # lighting at every fragment.  The floor is a single
    # quadrilateral, so without shaders, there would be a simple
    # gradient across the whole floor, with little discernable
    # pattern--dull indeed!  Shaders to the rescue!
    Shader = glLibShader()
    # light_ambient(light1) takes the ambient component of Light1
    # light_diffuse(light1) takes the diffuse component of Light1
    # light_specular_ph(light1) takes the phong specular component
    # of Light1
    # light_att(light1) takes the attenuation of Light1
    # light_spot returns the intensity of the light due to it's
    # being in a spot or not
    # Multiply the ambient by ambient color, diffuse by diffuse
    # color, specular by specular color and add to color
    Shader.render_equation("""
    vec3 ambient_light = light_ambient(light1).rgb+
                         light_ambient(light2).rgb+
                         light_ambient(light3).rgb+
                         light_ambient(light4).rgb;

    vec3 diffuse_light = light_diffuse(light1).rgb*light_att(light1)+
                         light_diffuse(light2).rgb*light_att(light2)+
                     2.0*light_diffuse(light3).rgb*light_att(light3)*light_spot(light3)+
                     2.0*light_diffuse(light4).rgb*light_att(light4)*light_spot(light4);

    vec3 specular_light = light_specular_ph(light1).rgb*light_att(light1)+
                          light_specular_ph(light2).rgb*light_att(light2)+
                      2.0*light_specular_ph(light3).rgb*light_att(light3)*light_spot(light3)+
                      2.0*light_specular_ph(light4).rgb*light_att(light4)*light_spot(light4);

    color.rgb += ambient_color.rgb*ambient_light;
    color.rgb += diffuse_color.rgb*diffuse_light;
    color.rgb += specular_color.rgb*specular_light;

    //color.rgb *= vec3(0.0,0.0,1.0);""")
    errors = Shader.compile()
    print(errors)
#    if errors != "":
#        pygame.quit()
#        raw_input(errors)
#        sys.exit()
#    else:
#        print "No errors to report with lighting shader (basiclighting.py)!"

def quit():
    global Light1, Light2, Light3, Light4
    glDisable(GL_LIGHTING)
    del Light1, Light2, Light3, Light4

def GetInput():
    global CameraRadius, LightRadius, LightUpDown, LightMulti
    mrel = pygame.mouse.get_rel()
    mpress = pygame.mouse.get_pressed()

    for event in pygame.event.get():
        if event.type == QUIT: quit(); pygame.quit(); exit(); sys.exit()
        if event.type == KEYDOWN and event.key == K_ESCAPE: return False
        if event.type == MOUSEBUTTONDOWN:
            if event.button == 5: CameraRadius += .2
            if event.button == 4: CameraRadius -= .2
            LightRadius = CameraRadius * 0.95
    # If the left mouse button is clicked,
    kpress = pygame.key.get_pressed()
    if kpress[K_LEFT]: CameraRotation[0] -= 1.0
    elif kpress[K_RIGHT]: CameraRotation[0] += 1.0
    elif kpress[K_UP]: CameraRotation[1] -= 1.0
    elif kpress[K_DOWN]: CameraRotation[1] += 1.0
    elif kpress[K_w]: LightMulti -= 0.02
    elif kpress[K_s]: LightMulti += 0.02
    elif kpress[K_a]: LightUpDown -= 1.0
    elif kpress[K_d]: LightUpDown += 1.0
    if kpress[K_LEFT] or \
            kpress[K_RIGHT] or \
            kpress[K_UP] or \
            kpress[K_DOWN] or \
            kpress[K_a] or \
            kpress[K_d] or \
            kpress[K_w] or \
            kpress[K_s]:
        print('CameraRotation=(%5.2f, %5.2f), LightUpDown=%5.2f, LightMulti=%5.2f' %
              (CameraRotation[0], CameraRotation[1], LightUpDown, LightMulti))

    # rotate the camera.
    if mpress[0]:
        CameraRotation[0] += mrel[0]
        CameraRotation[1] += mrel[1]


def SetCamera():
    gluLookAt(camerapos[0], camerapos[1], camerapos[2], 0, 0, 0, 0, 1, 0)


def Draw(Window):
    global camerapos
    Window.clear()
    View3D.set_view()
    # Calculate the camera's position using CameraRotation.
    # Basically just spherical coordinates.
    camerapos = [0 + CameraRadius*cos(radians(CameraRotation[0]))*cos(radians(CameraRotation[1])),
                 0 + CameraRadius*sin(radians(CameraRotation[1])),
                 0 + CameraRadius*sin(radians(CameraRotation[0]))*cos(radians(CameraRotation[1]))]

    lightpos = [0 + LightRadius*cos(radians(CameraRotation[0]))*cos(radians(CameraRotation[1])),
                0 + LightRadius*sin(radians(CameraRotation[1])),
                0 + LightRadius*sin(radians(CameraRotation[0]))*cos(radians(CameraRotation[1]))]
    SetCamera()
    Light1.set()
    Light2.set()

    Light3.set_pos(lightpos)
    _, t = divmod(floor(time.clock() * 100.0), 360)
    _lx, _ly, _lz = 0.0, -1.0, 0.0
    # _ly = -LightUpDown * 0.01
    # _ly = CameraRotation[1] / 90.0
    # _ly = sin(radians(-LightUpDown))
    _lx = -sin(radians(CameraRotation[0] + 90)) * LightMulti
    _lz = cos(radians(CameraRotation[0] + 90)) * LightMulti
    # _lx = -sin(radians(t)) * LightMulti
    # _lz = cos(radians(t)) * LightMulti
    '''
    if t < 9: _ly = -1.0
    elif t > 17: _ly = 1.0
    else: _ly = 0.0
    if t in (0, 1, 2, 9, 10, 11, 18, 19, 20): _lz = -1.0
    elif t in (6, 7, 8, 15, 16, 17, 24, 25, 26): _lz = 1.0
    else: _lz = 0.0
    if t in (0, 3, 6, 9, 12, 15, 18, 21, 24): _lx = -1.0
    elif t in (2, 5, 8, 11, 14, 17, 20, 23, 26): _lx = 1.0
    else: _lx = 0.0
    '''
    Light3.set_spot_dir([_lx, _ly, _lz])
    Light4.set_spot_dir([_lx, _ly, _lz])

    Light3.set()
    Light4.set()
    # Draw the floor
    #   Use the shader
    glLibUseShader(Shader)

    #   Save the current material, and then use
    #   a material that maximizes all parameters
    #   Then, lower the specular exponent to
    #   simulate a less shiny surface.
    # glPushAttrib(GL_LIGHTING_BIT)
    # glLibUseMaterial(GLLIB_MATERIAL_FULL)
    # glMaterialf(GL_FRONT_AND_BACK,GL_SHININESS,5.0)

    #   Draw the plane
    Plane.draw()
    # glBegin(GL_QUADS)
    # glVertex3f(0,0,0)
    # glVertex3f(1,0,0)
    # glVertex3f(1,0,1)
    # glVertex3f(0,0,1)
    # glEnd()

    #   Draw the object
    glPushMatrix()
    #       Translate it up.  Enclose in push and pop so that this
    #       transformation doesn't affect subsequent operations
    glTranslatef(0, 1, 0)
    #       Draw the object and restore modelview matrix
    # glLibUseShader(None)
    # glLibUseShader(Shader)
    # Object.draw_list()
    glCallList(dl)
    glPopMatrix()

    #   Restore the default material
    # glPopAttrib()

    #   Use the fixed function pipeline again
    glLibUseShader(None)

    # Draw the lights as points
    for light in [Light1,Light2,Light3,Light4]:
        light.draw_as_point()

    Window.flip()

Screen = (800,600)

def main():
    Window = glLibWindow(Screen, caption="OpenGL Library Demo: basiclighting.py",
                         multisample=False, position=GLLIB_CENTER,vsync=True)
    init(Screen)
    Clock = pygame.time.Clock()
    while True:
        GetInput()
        Draw(Window)
        Clock.tick(60)

    return

if __name__ == '__main__':
    main()
